// Package database
// @File: database.go
// @Description: 数据库初始化工厂
// @Author: YaoRuiQi
// @Date: 2025/8/25 14:26

package database

import (
	"context"
	"errors"
	"fmt"
	"time"

	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/driver/postgres"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// DBConfig
// @Description: 数据库配置
// @param Dialect 数据库类型: mysql | postgres | sqlite
// @param DSN 数据源连接字符串 (MySQL/PG 用)
// @param SQLiteFile SQLite 数据库文件路径
// @param LogLevel gorm日志级别: Silent/Error/Warn/Info
// @param MaxIdleConns 最大空闲连接数: 控制连接池里保持多少“闲置”的连接
//   - 过小: 每次请求都要新建连接，增加延迟
//   - 过大: 会占用数据库资源，浪费内存
//     建议: 通常设置为 CPU 核心数的 2~4 倍，或者和 MaxOpenConns 相同
//
// @param MaxOpenConns 最大打开连接数: 控制同时最多能有多少连接
//   - 过小: 并发高时容易排队等待，导致请求变慢
//   - 过大: 会把数据库压垮，出现 Too many connections 错误
//     建议: 根据数据库配置和服务器性能决定
//     MySQL 默认 max_connections=151，可以设置成 50~100
//     PostgreSQL 默认 max_connections=100，可以设置成 50~80
//     一般经验值 = 应用峰值 QPS ÷ 单个连接能支撑的 QPS
//
// @param ConnMaxLife 连接最大生命周期: 控制单个连接的最长存活时间
//   - 过长: 连接可能因网络/防火墙/数据库重启而失效
//   - 过短: 频繁重建连接，增加开销
//     建议: 一般设为 30 分钟 ~ 2 小时，比数据库的 wait_timeout (MySQL) 小一点
//     MySQL: wait_timeout 默认 8 小时，可以设 1 小时
//     PostgreSQL: 默认没限制，可以设 30 分钟~1 小时
//
// @param InitTimeout 初始化超时时间
type DBConfig struct {
	Dialect      string
	DSN          string
	SQLiteFile   string
	LogLevel     logger.LogLevel
	MaxIdleConns int
	MaxOpenConns int
	ConnMaxLife  time.Duration
	InitTimeout  time.Duration
}

var DB *gorm.DB

// InitDB
//
//	@Description: 初始化数据库实例
//	@param cfg
//	@return *gorm.DB
//	@return error
func InitDB(log *zap.Logger, cfg DBConfig) error {
	// 设置默认值
	if cfg.MaxIdleConns <= 0 {
		return errors.New("maxIdleConns 必须大于 0")
	}
	if cfg.MaxOpenConns <= 0 {
		return errors.New("maxOpenConns 必须大于 0")
	}
	if cfg.ConnMaxLife <= 0 {
		return errors.New("connMaxLife 必须大于 0")
	}
	if cfg.InitTimeout == 0 {
		return errors.New("timeout 必须大于 0")
	}

	// 选择 Dialector
	var dialector gorm.Dialector
	switch cfg.Dialect {
	case "mysql":
		dialector = mysql.Open(cfg.DSN)
	case "postgres":
		dialector = postgres.Open(cfg.DSN)
	case "sqlite":
		if cfg.SQLiteFile == "" {
			return fmt.Errorf("sqlite 文件路径不能为空")
		}
		dialector = sqlite.Open(cfg.SQLiteFile)
	default:
		return fmt.Errorf("不支持的数据库类型: %s", cfg.Dialect)
	}

	// 初始化 DB
	db, err := gorm.Open(dialector, &gorm.Config{
		Logger: logger.Default.LogMode(cfg.LogLevel),
	})
	if err != nil {
		return fmt.Errorf("gorm open 失败: %w", err)
	}

	sqlDB, err := db.DB()
	if err != nil {
		return fmt.Errorf("获取底层连接池失败: %w", err)
	}

	// 设置连接池
	sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
	sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
	sqlDB.SetConnMaxLifetime(cfg.ConnMaxLife)

	// 用 context 做超时 ping
	ctx, cancel := context.WithTimeout(context.Background(), cfg.InitTimeout)
	defer cancel()

	pingCh := make(chan error, 1)
	go func() {
		pingCh <- sqlDB.Ping()
	}()

	select {
	case <-ctx.Done():
		return fmt.Errorf("数据库初始化超时(%s)", cfg.InitTimeout)
	case err := <-pingCh:
		if err != nil {
			return fmt.Errorf("数据库 ping 失败: %w", err)
		}
	}

	log.Info("数据库连接成功",
		zap.String("dialect", cfg.Dialect),
		zap.Int("maxIdle", cfg.MaxIdleConns),
		zap.Int("maxOpen", cfg.MaxOpenConns),
		zap.Duration("connMaxLife", cfg.ConnMaxLife),
	)

	DB = db
	return nil
}
